<?php

namespace restphp\aop;

use restphp\core\RestBuildV2;
use restphp\utils\RestStringUtils;
use restphp\validate\RestValidate;

class AspectBuilder {
    private static $_defaultAspects = array();
    private static $_allClasses = array();
    private static $_fileContent = array();
    public static function build (){
        echo "\n<br />start build aop relation";
        //扫描所有类
        $arrFiles = array();
        $arrDir = explode("|", DIR_BUILD);
        foreach ($arrDir as $eachBuildDir) {
            RestBuildV2::loadPHPFiles($arrFiles, $eachBuildDir . DIRECTORY_SEPARATOR);
            foreach ($arrFiles as &$file) {
                $content = file_get_contents($file);
                $file = str_replace($eachBuildDir, '', $file);
                self::$_fileContent[$file] = $content;
            }
        }

        self::$_allClasses = array();

        self::_foundAspectImpls($arrFiles);

        self::_structAspectUsedInClass();

        self::_structAspectUsedInFunction();
        echo "\n<br />build aop relation success!";
    }

    /**
     * 扫描用户自定义的切面实现类.
     * @param $arrFiles
     * @return void
     * @throws \ReflectionException
     */
    private static function _foundAspectImpls($arrFiles) {
        foreach($arrFiles as $strFile) {
            $fileContent = self::$_fileContent[$strFile];
            $strCallClass = substr($strFile, 0, strlen($strFile) - 4);
            $strCallClass = str_replace("\\", "/", $strCallClass);
            if (RestStringUtils::startWith($strCallClass, 'src/php/')) {
                $strCallClass = substr($strCallClass, strlen('src/php/'));
            }
            $strCallClass = str_replace("/", "\\", $strCallClass);
            $reflection = new \ReflectionClass($strCallClass);

            self::$_allClasses[] = array(
                'instance'  => $reflection,
                'content'   => $fileContent
            );


            $name = $reflection->getName();
            $doc = $reflection->getDocComment();
            $arrDoc = RestValidate::clearDocToArr($doc);
            if (empty($arrDoc)) {
                continue;
            }

            foreach ($arrDoc as $line) {
                if (!preg_match('/@Aspect\\s*\(/', $line)) {
                    continue;
                }
                $aspectName = trim(substr($line, strpos($line, '(') + 1, strpos($line, ')') - strpos($line, '(') - 1));
                if (strpos($aspectName, '"')) {
                    $aspectName = trim(substr($aspectName, strpos($aspectName, '"')));
                }
                if (strpos($aspectName, "'")) {
                    $aspectName = trim(substr($aspectName, strpos($aspectName, "'")));
                }
                $aspectName = RestValidate::clearMessageBoundary($aspectName);
                if (isset(self::$_defaultAspects[$aspectName])) {
                    echo "Waring: Duplicate Aspect Name: $aspectName\n";
                }
                self::$_defaultAspects[$aspectName] = $name;
            }
        }
    }

    private static function _structAspectUsedInClass() {
        // 发现引用关系
        $arrAllAopMap = array();
        foreach (self::$_allClasses as $arrClass) {
            $reflection = $arrClass['instance'];
            $doc = $reflection->getDocComment();
            $arrDoc = RestValidate::clearDocToArr($doc);
            $arrClassAopMap = array();
            foreach ($arrDoc as $line) {
                foreach (self::$_defaultAspects as $aopName => $aopClass) {
                    if (preg_match("/@${aopName}/", $line)) {
                        $arrClassAopMap[$aopClass] = self::_getProcessParam($line);
                    }
                }
            }

            if (!empty($arrClassAopMap)) {
                $arrAllAopMap[$reflection->getName()] = $arrClassAopMap;
            }
        }

        // 写引用配置
        $strContent = '<?php' . "\n" . 'return array(' . "\n";
        foreach ($arrAllAopMap as $useClass => $arrAop) {
            $strContent .= "\t" . '"' . $useClass . '" => array(' . "\n";
            foreach ($arrAop as $aop => $arrProcessParam) {
                $strContent .= "\t\t" . '"' . $aop . '"=>array(' . "\n";
                foreach ($arrProcessParam as $key => $value) {
                    $strContent .= "\t\t\t" . '"' . $key . '"=>"' . $value . '",' . "\n";
                }
                $strContent .= "\t\t" . '),' . "\n";
            }
            $strContent .= "\t" . '),' . "\n";
        }
        $strContent .= ');';

        $targetDir = DIR_BUILD_TARGET . '/_annotation_';
        if (!file_exists($targetDir)) {
            mkdir($targetDir);
        }

        file_put_contents($targetDir . '/_restphp_aop_class_map.php', $strContent);
    }

    private static function _structAspectUsedInFunction() {
        foreach (self::$_allClasses as $arrClass) {
            $reflection = $arrClass['instance'];
            $arrMethods = $reflection->getMethods();
            $className = $reflection->getName();
            $needStruct = false;
            $arrMethodWithAop = array();
            foreach ($arrMethods as $method) {
                $doc = $method->getDocComment();
                $arrDoc = RestValidate::clearDocToArr($doc);
                $arrMethodAnnotations = array();
                foreach ($arrDoc as $item) {
                    foreach (self::$_defaultAspects as $aopName => $aopClass) {
                        if (!preg_match("/@${aopName}/", $item)) {
                            continue;
                        }
                        $needStruct = true;
                        $arrMethodAnnotations[$aopName] = $item;
                    }
                }
                $arrMethodWithAop[$method->getName()] = $arrMethodAnnotations;
            }

            // 重构类方法
            if ($needStruct) {
                //echo $arrClass['content'];
                $content = $arrClass['content'];
                $arrContents = explode("\n", $content);
                foreach ($arrMethodWithAop as $methodName => $arrAnnotations) {
                    if (empty($arrAnnotations)) {
                        continue;
                    }
                    foreach ($arrContents as &$line) {
                        $strRegex = '/function\\s*'. $methodName .'\\s*\(/';
                        if (preg_match($strRegex, $line)) {
                            $sourceLine = "${line}";
                            if (strpos($sourceLine, '{')>-1) {
                                $sourceLine = substr($sourceLine, 0, strpos($sourceLine, '{'));
                            }
                            $sourceLine .= "\t{\n";
                            $beforeCode = '';
                            $afterCode = '';
                            self::_callAspectCode($arrAnnotations, $beforeCode,  $afterCode);
                            $sourceLine .= $beforeCode;
                            $sourceLine .= "\t\t" . '$result = call_user_func_array(array($this, "_annotation_' . $methodName . '"), func_get_args());' . "\n";
                            $sourceLine .= $afterCode;
                            $sourceLine .= "\t\t" . 'return $result;' . "\n";
                            $sourceLine .= "\t}\n\n";
                            $line = preg_replace($strRegex, 'function _annotation_' . $methodName . '(', $line);
                            $line = $sourceLine . $line;
                        }
                    }
                }
                /*echo $className . "\n";
                foreach ($arrContents as $line) {
                    echo $line."\n";
                }*/
                self::_writeAliasFile($className, $arrContents);
            }
        }

    }

    static function _writeAliasFile($className, $arrContents)
    {
        $strContent = '';
        foreach ($arrContents as $line) {
            $strContent .= $line."\n";
        }

        $targetDir = DIR_BUILD_TARGET . '/_annotation_';
        if (!file_exists($targetDir)) {
            mkdir($targetDir);
        }
        $arrClassName = explode('\\', $className);
        $i = 0;
        $count = count($arrClassName);
        foreach ($arrClassName as $name) {
            if (++$i < $count) {
                $targetDir .= '/' . $name;
                if (!file_exists($targetDir)) {
                    mkdir($targetDir);
                }
                continue;
            }
            $strFileName = $targetDir . '/' . $name . '.php';
            file_put_contents($strFileName, $strContent);
        }
    }

    private static function _getProcessParam($annotation) {
        $arrStaticParam = array();
        if (strpos($annotation, '(') > -1) {
            $paramContent = substr($annotation, strpos($annotation, '(') + 1, strpos($annotation, ')') - strpos($annotation, '(') - 1);
            $arrParamItem = explode(',', $paramContent);
            foreach ($arrParamItem as $item) {
                $arrParam = explode('=', $item);
                if (count($arrParam) == 1) {
                    $arrStaticParam['value'] = trim(RestValidate::clearMessageBoundary($arrParam[0]));
                } elseif (count($arrParam) == 2) {
                    $key = $arrParam[0];
                    $val = trim(RestValidate::clearMessageBoundary($arrParam[1]));
                    $arrStaticParam[$key] = $val;
                }
            }
        }
        return $arrStaticParam;
    }

    static function _callAspectCode($arrAnnotations, &$beforeCode = '', &$afterCode = '')
    {
        foreach ($arrAnnotations as $aopName => $annotation) {
            $aopClassName = self::$_defaultAspects[$aopName];
            $arrStaticParam = self::_getProcessParam($annotation);

            $aopInstance = new \ReflectionClass($aopClassName);
            $hasBefore = $aopInstance->hasMethod('before');
            $hasAfter = $aopInstance->hasMethod('after');

            if ($hasBefore || $hasAfter) {
                $beforeCode .= "\t\t" . '$aop' . $aopName . ' = new \\' . $aopClassName . '();' . "\n";
            }
            if ($hasBefore) {
                $beforeCode .= "\t\t" . '$aop' . $aopName . '->before(\restphp\aop\AopParam::instance(array(' . "\n";
            }
            if ($hasAfter) {
                $afterCode .= "\t\t" . '$aop' . $aopName . '->after(\restphp\aop\AopParam::instance(array(' . "\n";
            }
            foreach ($arrStaticParam as $key => $val) {
                $strCode = "\t\t\t\"" . $key . '" => "' . $val . '",' . "\n";
                if ($hasBefore) {
                    $beforeCode .= $strCode;
                }
                if ($hasAfter) {
                    $afterCode .= $strCode;
                }
            }
            if ($hasBefore) {
                $beforeCode .= "\t\t" . '), $this, __FUNCTION__, func_get_args()));' . "\n\n";
            }
            if ($hasAfter) {
                $afterCode .= "\t\t" . '), $this, __FUNCTION__, func_get_args(), $result));' . "\n\n";
            }
        }
    }
}